//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//
/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using EffiProz.Core.Lib;
using EffiProz.Core.Persist;
using EffiProz.Core.RowIO;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Rights;
using EffiProz.Core.Navigators;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Results;


// fredt@users 20020130 - patch 491987 by jimbag@users - made optional
// fredt@users 20020405 - patch 1.7.0 by fredt - quoted identifiers
// for sql standard quoted identifiers for column and table names and aliases
// applied to different places
// fredt@users 20020225 - patch 1.7.0 - restructuring
// some methods moved from Database.java, some rewritten
// changes to several methods
// fredt@users 20020225 - patch 1.7.0 - ON DELETE CASCADE
// fredt@users 20020225 - patch 1.7.0 - named constraints
// boucherb@users 20020225 - patch 1.7.0 - multi-column primary keys
// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
// tony_lai@users 20020820 - patch 595099 - user defined PK name
// tony_lai@users 20020820 - patch 595172 - drop constraint fix
// kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE | SET NULL | SET DEFAULT
// kloska@users 20021112 - patch 1.7.2 - ON DELETE SET NULL | SET DEFAULT
// fredt@users 20021210 - patch 1.7.2 - better ADD / DROP INDEX for non-CACHED tables
// fredt@users 20030901 - patch 1.7.2 - allow multiple nulls for UNIQUE columns
// fredt@users 20030901 - patch 1.7.2 - reworked IDENTITY support
// achnettest@users 20040130 - patch 878288 - bug fix for new indexes _in memory tables by Arne Christensen
// boucherb@users 20040327 - doc 1.7.2 - javadoc updates
// boucherb@users 200404xx - patch 1.7.2 - proper uri for getCatalogName
// fredt@users 20050000 - 1.8.0 updates _in several areas
// fredt@users 20050220 - patch 1.8.0 enforcement of DECIMAL precision/scale

namespace EffiProz.Core
{

    /**
     *  Holds the data structures and methods for creation of a database table.
     *
     *
     * Extensively rewritten and extended _in successive versions of HSQLDB.
     *
     * @author Thomas Mueller (Hypersonic SQL Group)
     * @version 1.8.0
     * @since Hypersonic SQL
     */
    public class Table : TableBase, SchemaObject
    {
        public static Table[] emptyArray = new Table[] { };

        // main properties
        public QNameManager.QName tableName;

        //
        public HashMappedList columnList;          // columns in table
        public int identityColumn;      // -1 means no such column
        public NumberSequence identitySequence;    // next value of identity column

        // -----------------------------------------------------------------------
        public Constraint[] constraintList;            // constrainst for the table
        public Constraint[] fkPath;                    //
        public Constraint[] fkConstraints;             //
        public Constraint[] fkMainConstraints;
        public Constraint[] checkConstraints;
        public TriggerDef[] triggerList;
        public TriggerDef[][] triggerLists;              // array of trigger lists
        public Expression[] colDefaults;               // fredt - expressions of DEFAULT values
        public bool[] colGenerated;              // fredt - expressions of DEFAULT values
        protected int[] defaultColumnMap;          // fred - holding 0,1,2,3,...
        private bool hasDefaultValues;          //fredt - shortcut for above
        public RangeVariable[] defaultRanges;

        //
        public Table(Database database, QNameManager.QName name, int type)
        {

            base.database = database;
            tableName = name;
            persistenceId = database.persistentStoreCollection.getNextId();

            switch (type)
            {

                case SYSTEM_SUBQUERY:
                    persistenceScope = SCOPE_STATEMENT;
                    isSessionBased = true;
                    break;

                case SYSTEM_TABLE:
                    persistenceScope = SCOPE_FULL;
                    _isSchemaBased = true;
                    break;

                case CACHED_TABLE:
                    if (DatabaseURL.isFileBasedDatabaseType(database.getType()))
                    {
                        persistenceScope = SCOPE_FULL;
                        _isSchemaBased = true;
                        _isCached = true;
                        isLogged = !database.isFilesReadOnly();

                        break;
                    }

                    type = MEMORY_TABLE;
                    goto case MEMORY_TABLE;
                // fall through
                case MEMORY_TABLE:
                    persistenceScope = SCOPE_FULL;
                    _isSchemaBased = true;
                    isLogged = !database.isFilesReadOnly();
                    break;

                case TEMP_TABLE:
                    persistenceScope = SCOPE_TRANSACTION;
                    _isTemp = true;
                    _isSchemaBased = true;
                    isSessionBased = true;
                    break;

                case TEMP_TEXT_TABLE:
                    persistenceScope = SCOPE_SESSION;

                    if (!DatabaseURL.isFileBasedDatabaseType(database.getType()))
                    {
                        throw Error.error(ErrorCode.DATABASE_IS_MEMORY_ONLY);
                    }

                    _isSchemaBased = true;
                    isSessionBased = true;
                    _isTemp = true;
                    _isText = true;
                    _isReadOnly = true;
                    break;

                case TEXT_TABLE:
                    persistenceScope = SCOPE_FULL;

                    if (!DatabaseURL.isFileBasedDatabaseType(database.getType()))
                    {
                        throw Error.error(ErrorCode.DATABASE_IS_MEMORY_ONLY);
                    }

                    _isSchemaBased = true;
                    _isText = true;
                    break;

                case VIEW_TABLE:
                    persistenceScope = SCOPE_STATEMENT;
                    _isSchemaBased = true;
                    isSessionBased = true;
                    _isView = true;
                    break;

                case RESULT_TABLE:
                    persistenceScope = SCOPE_SESSION;
                    isSessionBased = true;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Table");
            }

            // type may have changed above for CACHED tables
            tableType = type;
            primaryKeyCols = null;
            primaryKeyTypes = null;
            identityColumn = -1;
            columnList = new HashMappedList();
            indexList = IndexAVL.emptyArray;
            constraintList = Constraint.emptyArray;
            fkPath = Constraint.emptyArray;
            fkConstraints = Constraint.emptyArray;
            fkMainConstraints = Constraint.emptyArray;
            checkConstraints = Constraint.emptyArray;
            triggerList = TriggerDef.emptyArray;
            triggerLists = new TriggerDef[TriggerDef.NUM_TRIGS][];

            for (int i = 0; i < TriggerDef.NUM_TRIGS; i++)
            {
                triggerLists[i] = TriggerDef.emptyArray;
            }

            if (database.isFilesReadOnly() && isFileBased())
            {
                this._isReadOnly = true;
            }

            if (!_isTemp)
            {
                createDefaultStore();
            }
        }

        public Table(Table table, QNameManager.QName name)
        {

            persistenceScope = SCOPE_STATEMENT;
            name.schema = SqlInvariants.SYSTEM_SCHEMA_QNAME;
            this.tableName = name;
            this.database = table.database;
            this.tableType = RESULT_TABLE;
            this.columnList = table.columnList;
            this.columnCount = table.columnCount;
            this.indexList = IndexAVL.emptyArray;
            this.constraintList = Constraint.emptyArray;

            createPrimaryKey();
        }

        public void createDefaultStore()
        {

            store = database.logger.newStore(null,
                                             database.persistentStoreCollection,
                                             this, true);
        }

        public virtual int getType()
        {
            return SchemaObjectTypes.TABLE;
        }

        /**
         *  Returns the QNameManager.QName object fo the table
         */
        public virtual QNameManager.QName getName()
        {
            return tableName;
        }

        /**
         * Returns the catalog name or null, depending on a database property.
         */
        public virtual QNameManager.QName getCatalogName()
        {
            return database.getCatalogName();
        }

        /**
         * Returns the schema name.
         */
        public virtual QNameManager.QName getSchemaName()
        {
            return tableName.schema;
        }

        public virtual Grantee getOwner()
        {
            return tableName.schema.owner;
        }

        public virtual OrderedHashSet getReferences()
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0; i < colTypes.Length; i++)
            {
                if (colTypes[i].isDomainType() || colTypes[i].isDistinctType())
                {
                    QNameManager.QName name = ((SchemaObject)colTypes[i]).getName();

                    set.add(name);
                }
            }

            return set;
        }

        public virtual OrderedHashSet getComponents()
        {

            OrderedHashSet set = new OrderedHashSet();

            set.addAll(constraintList);
            set.addAll(triggerList);

            for (int i = 0; i < indexList.Length; i++)
            {
                if (!indexList[i].isConstraint())
                {
                    set.add(indexList[i]);
                }
            }

            return set;
        }

        public virtual void compile(Session session, SchemaObject parentObject)
        {

            for (int i = 0; i < columnCount; i++)
            {
                ColumnSchema column = getColumn(i);

                column.compile(session, this);
            }
        }

        public String[] getSQL(OrderedHashSet resolved, OrderedHashSet unresolved)
        {

            for (int i = 0; i < constraintList.Length; i++)
            {
                Constraint c = constraintList[i];

                if (c.isForward)
                {
                    unresolved.add(c);
                }
                else if (c.getConstraintType() == SchemaObjectConstraintTypes
                      .UNIQUE || c.getConstraintType() == SchemaObjectConstraintTypes.PRIMARY_KEY)
                {
                    resolved.add(c.getName());
                }
            }

            EfzArrayList list = new EfzArrayList();

            list.add(getSQL());

            if (!_isTemp && !_isText && hasIdentityColumn())
            {
                list.add(NumberSequence.getRestartSQL(this));
            }

            for (int i = 0; i < indexList.Length; i++)
            {
                if (!indexList[i].isConstraint())
                {
                    list.add(indexList[i].getSQL());
                }
            }

            String[] array = new String[list.size()];

            list.toArray(array);

            return array;
        }

        public String[] getSQLForTextSource(bool withHeader)
        {

            // readonly for TEXT tables only
            if (isText())
            {
                EfzArrayList list = new EfzArrayList();
                bool rdy = isDataReadOnly();

                if (rdy)
                {
                    StringBuilder sb = new StringBuilder(64);

                    sb.Append(Tokens.T_SET).Append(' ').Append(
                        Tokens.T_TABLE).Append(' ');
                    sb.Append(getName().getSchemaQualifiedStatementName());
                    sb.Append(' ').Append(Tokens.T_READ).Append(' ');
                    sb.Append(Tokens.T_ONLY);
                    list.add(sb.ToString());
                }

                // data source
                String dataSource = ((TextTable)this).getDataSourceDDL();

                if (dataSource != null)
                {
                    list.add(dataSource);
                }

                // header
                String header = ((TextTable)this).getDataSourceHeader();

                if (withHeader && header != null && !rdy)
                {
                    list.add(header);
                }

                String[] array = new String[list.size()];

                list.toArray(array);

                return array;
            }
            else
            {
                return null;
            }
        }

        public String[] getTriggerSQL()
        {

            String[] array = new String[triggerList.Length];

            for (int i = 0; i < triggerList.Length; i++)
            {
                array[i] = triggerList[i].getSQL();
            }

            return array;
        }

        public virtual String getSQL()
        {

            StringBuilder sb = new StringBuilder();

            sb.Append(Tokens.T_CREATE).Append(' ');

            if (isTemp())
            {
                sb.Append(Tokens.T_GLOBAL).Append(' ');
                sb.Append(Tokens.T_TEMPORARY).Append(' ');
            }
            else if (isText())
            {
                sb.Append(Tokens.T_TEXT).Append(' ');
            }
            else if (isCached())
            {
                sb.Append(Tokens.T_CACHED).Append(' ');
            }
            else
            {
                sb.Append(Tokens.T_MEMORY).Append(' ');
            }

            sb.Append(Tokens.T_TABLE).Append(' ');
            sb.Append(getName().getSchemaQualifiedStatementName());
            sb.Append('(');

            int columns = getColumnCount();
            int[] pk = getPrimaryKey();
            Constraint pkConst = getPrimaryConstraint();

            for (int j = 0; j < columns; j++)
            {
                ColumnSchema column = getColumn(j);
                String colname = column.getName().statementName;
                SqlType type = column.getDataType();

                if (j > 0)
                {
                    sb.Append(',');
                }

                sb.Append(colname);
                sb.Append(' ');
                sb.Append(type.getTypeDefinition());

                String defaultString = column.getDefaultSQL();

                if (defaultString != null)
                {
                    sb.Append(' ').Append(Tokens.T_DEFAULT).Append(' ');
                    sb.Append(defaultString);
                }

                if (column.isIdentity())
                {
                    sb.Append(' ').Append(column.getIdentitySequence().getSQL());
                }

                if (!column.isNullable())
                {
                    Constraint c = getNotNullConstraintForColumn(j);

                    if (c != null && !c.getName().isReservedName())
                    {
                        sb.Append(' ').Append(Tokens.T_CONSTRAINT).Append(
                            ' ').Append(c.getName().statementName);
                    }

                    sb.Append(' ').Append(Tokens.T_NOT).Append(' ').Append(
                        Tokens.T_NULL);
                }

                if (column.isGenerated())
                {
                    sb.Append(' ').Append(Tokens.T_GENERATED).Append(' ');
                    sb.Append(Tokens.T_ALWAYS).Append(' ').Append(
                        Tokens.T_AS).Append(Tokens.T_OPENBRACKET);
                    sb.Append(column.getGeneratingExpression().getSQL());
                    sb.Append(Tokens.T_CLOSEBRACKET);
                }

                if (pk.Length == 1 && j == pk[0]
                        && pkConst.getName().isReservedName())
                {
                    sb.Append(' ').Append(Tokens.T_PRIMARY).Append(' ').Append(
                        Tokens.T_KEY);
                }
            }

            Constraint[] constraintList = getConstraints();

            for (int j = 0, vSize = constraintList.Length; j < vSize; j++)
            {
                Constraint c = constraintList[j];

                if (!c.isForward)
                {
                    String d = c.getSQL();

                    if (d.Length > 0)
                    {
                        sb.Append(',');
                        sb.Append(d);
                    }
                }
            }

            sb.Append(')');

            if (onCommitPreserve())
            {
                sb.Append(' ').Append(Tokens.T_ON).Append(' ');
                sb.Append(Tokens.T_COMMIT).Append(' ').Append(Tokens.T_PRESERVE);
                sb.Append(' ').Append(Tokens.T_ROWS);
            }

            return sb.ToString();
        }

        public String getIndexRootsSQL(int[] roots)
        {

            StringBuilder sb = new StringBuilder(128);

            sb.Append(Tokens.T_SET).Append(' ').Append(Tokens.T_TABLE).Append(' ');
            sb.Append(getName().getSchemaQualifiedStatementName());
            sb.Append(' ').Append(Tokens.T_INDEX).Append(' ').Append('\'');
            sb.Append(getIndexRoots(roots));
            sb.Append('\'');

            return sb.ToString();
        }

        /**
         * Used to create row id's
         */
        public override int getId()
        {
            return tableName.GetHashCode();
        }

        public bool isText()
        {
            return _isText;
        }

        public bool isTemp()
        {
            return _isTemp;
        }

        public bool isReadOnly()
        {
            return _isReadOnly;
        }

        public bool isView()
        {
            return _isView;
        }

        public bool isCached()
        {
            return _isCached;
        }

        public virtual bool isDataReadOnly()
        {
            return _isReadOnly;
        }

        /**
         * returns false if the table has to be recreated in order to add / drop
         * indexes. Only CACHED tables return false.
         */
        public bool isIndexingMutable()
        {
            return !isIndexCached();
        }

        /**
         *  Returns true if table is CACHED
         */
        public virtual bool isIndexCached()
        {
            return _isCached;
        }

        /**
         * Used by INSERT, DELETE, UPDATE operations
         */
        public virtual void checkDataReadOnly()
        {

            if (_isReadOnly)
            {
                throw Error.error(ErrorCode.DATA_IS_READONLY);
            }
        }

        // ----------------------------------------------------------------------------
        // akede@users - 1.7.2 patch Files readonly
        public virtual void setDataReadOnly(bool value)
        {

            // Changing the Read-Only mode for the table is only allowed if the
            // the database can realize it.
            if (!value && database.isFilesReadOnly() && isFileBased())
            {
                throw Error.error(ErrorCode.DATA_IS_READONLY);
            }

            _isReadOnly = value;
        }

        /**
         * Text or Cached Tables are normally file based
         */
        public bool isFileBased()
        {
            return _isCached || _isText;
        }

        /**
         *  Adds a constraint.
         */
        public void addConstraint(Constraint c)
        {

            int index = c.getConstraintType()
                        == SchemaObjectConstraintTypes.PRIMARY_KEY ? 0
                                                                    : constraintList
                                                                        .Length;

            constraintList =
                (Constraint[])ArrayUtil.toAdjustedArray(constraintList, c, index,
                    1);

            updateConstraintLists();
        }

        public void updateConstraintPath()
        {

            if (fkMainConstraints.Length == 0)
            {
                return;
            }

            OrderedHashSet list = new OrderedHashSet();

            getConstraintPath(defaultColumnMap, list);

            if (list.size() == 0)
            {
                return;
            }

            fkPath = new Constraint[list.size()];

            list.toArray(fkPath);
            verifyConstraintsIntegrity();
        }

        public void updateConstraintLists()
        {

            int fkCount = 0;
            int mainCount = 0;
            int checkCount = 0;

            for (int i = 0; i < constraintList.Length; i++)
            {
                switch (constraintList[i].getConstraintType())
                {

                    case SchemaObjectConstraintTypes.FOREIGN_KEY:
                        fkCount++;
                        break;

                    case SchemaObjectConstraintTypes.MAIN:
                        mainCount++;
                        break;

                    case SchemaObjectConstraintTypes.CHECK:
                        if (constraintList[i].isNotNull())
                        {
                            break;
                        }

                        checkCount++;
                        break;
                }
            }

            fkConstraints = fkCount == 0 ? Constraint.emptyArray
                                             : new Constraint[fkCount];
            fkCount = 0;
            fkMainConstraints = mainCount == 0 ? Constraint.emptyArray
                                               : new Constraint[mainCount];
            mainCount = 0;
            checkConstraints = checkCount == 0 ? Constraint.emptyArray
                                                : new Constraint[checkCount];
            checkCount = 0;

            for (int i = 0; i < constraintList.Length; i++)
            {
                switch (constraintList[i].getConstraintType())
                {

                    case SchemaObjectConstraintTypes.FOREIGN_KEY:
                        fkConstraints[fkCount] = constraintList[i];

                        fkCount++;
                        break;

                    case SchemaObjectConstraintTypes.MAIN:
                        fkMainConstraints[mainCount] = constraintList[i];

                        mainCount++;
                        break;

                    case SchemaObjectConstraintTypes.CHECK:
                        if (constraintList[i].isNotNull())
                        {
                            break;
                        }

                        checkConstraints[checkCount] = constraintList[i];

                        checkCount++;
                        break;
                }
            }
        }

        public void verifyConstraintsIntegrity()
        {

            for (int i = 0; i < constraintList.Length; i++)
            {
                Constraint c = constraintList[i];

                if (c.getConstraintType() == SchemaObjectConstraintTypes.CHECK)
                {
                    continue;
                }

                if (c.getMain() != null
                        && c.getMain()
                           != database.schemaManager.getUserTable(null,
                               c.getMain().getName()))
                {
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "table constraint");
                }

                if (c.getRef() != null
                        && c.getRef()
                           != database.schemaManager.getUserTable(null,
                               c.getRef().getName()))
                {
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "table constraint");
                }
            }
        }

        /**
         *  Returns the list of constraints.
         */
        public Constraint[] getConstraints()
        {
            return constraintList;
        }

        /**
         *  Returns the primary constraint.
         */
        public Constraint getPrimaryConstraint()
        {
            return primaryKeyCols.Length == 0 ? null
                                              : constraintList[0];
        }

        public void getConstraintPath(int[] columnMap, OrderedHashSet list)
        {

            for (int i = 0; i < constraintList.Length; i++)
            {
                if (constraintList[i].hasTriggeredAction())
                {
                    int[] mainColumns = constraintList[i].getMainColumns();

                    if (ArrayUtil.countCommonElements(columnMap, mainColumns)
                            > 0)
                    {
                        if (list.add(constraintList[i]))
                        {
                            constraintList[i].getRef().getConstraintPath(
                                constraintList[i].getRefColumns(), list);
                        }
                    }
                }
            }
        }

        public Constraint getNotNullConstraintForColumn(int colIndex)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.isNotNull() && c.notNullColumnIndex == colIndex)
                {
                    return c;
                }
            }

            return null;
        }

        /**
         * Returns the UNIQUE or PK constraint with the given column signature.
         */
        public Constraint getUniqueConstraintForColumns(int[] cols)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.isUniqueWithColumns(cols))
                {
                    return c;
                }
            }

            return null;
        }

        /**
         * Returns the UNIQUE or PK constraint with the given column signature.
         * Modifies the composition of refTableCols if necessary.
         */
        public Constraint getUniqueConstraintForColumns(int[] mainTableCols,
                int[] refTableCols)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];
                int type = c.getConstraintType();

                if (type != SchemaObjectConstraintTypes.UNIQUE
                        && type != SchemaObjectConstraintTypes.PRIMARY_KEY)
                {
                    continue;
                }

                int[] constraintCols = c.getMainColumns();

                if (constraintCols.Length != mainTableCols.Length)
                {
                    continue;
                }

                if (ArrayUtil.areEqual(constraintCols, mainTableCols,
                                       mainTableCols.Length, true))
                {
                    return c;
                }

                if (ArrayUtil.areEqualSets(constraintCols, mainTableCols))
                {
                    int[] newRefTableCols = new int[mainTableCols.Length];

                    for (int j = 0; j < mainTableCols.Length; j++)
                    {
                        int pos = ArrayUtil.find(constraintCols, mainTableCols[j]);

                        newRefTableCols[pos] = refTableCols[j];
                    }

                    for (int j = 0; j < mainTableCols.Length; j++)
                    {
                        refTableCols[j] = newRefTableCols[j];
                    }

                    return c;
                }
            }

            return null;
        }

        /**
         *  Returns any foreign key constraint equivalent to the column sets
         */
        public Constraint getFKConstraintForColumns(Table tableMain, int[] mainCols,
                                              int[] refCols)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.isEquivalent(tableMain, mainCols, this, refCols))
                {
                    return c;
                }
            }

            return null;
        }

        /**
         *  Returns any unique Constraint using this index
         *
         * @param  index
         */
        public Constraint getUniqueOrPKConstraintForIndex(Index index)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.getMainIndex() == index && (c
                        .getConstraintType() == SchemaObjectConstraintTypes
                        .UNIQUE || c.getConstraintType() == SchemaObjectConstraintTypes.PRIMARY_KEY))
                {
                    return c;
                }
            }

            return null;
        }

        /**
         *  Returns the next constraint of a given type
         *
         * @param  from
         * @param  type
         */
        public int getNextConstraintIndex(int from, int type)
        {

            for (int i = from, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.getConstraintType() == type)
                {
                    return i;
                }
            }

            return -1;
        }

        /**
         *  Performs the table level checks and adds a column to the table at the
         *  DDL level. Only used at table creation, not at alter column.
         */
        public void addColumn(ColumnSchema column)
        {

            String name = column.getName().name;

            if (findColumn(name) >= 0)
            {
                throw Error.error(ErrorCode.X_42504, name);
            }

            if (column.isIdentity())
            {
                if (identityColumn != -1)
                {
                    throw Error.error(ErrorCode.X_42525, name);
                }

                identityColumn = getColumnCount();
                identitySequence = column.getIdentitySequence();
            }

            addColumnNoCheck(column);
        }

        public void addColumnNoCheck(ColumnSchema column)
        {

            if (primaryKeyCols != null)
            {
                throw Error.runtimeError(ErrorCode.U_S0500, "Table");
            }

            columnList.add(column.getName().name, column);

            columnCount++;

            if (column.getDataType().isLobType())
            {
                hasLobColumn = true;
            }
        }

        public bool hasIdentityColumn()
        {
            return identityColumn != -1;
        }

        public long getNextIdentity()
        {
            return identitySequence.peek();
        }

        /**
         * Match two valid, equal length, columns arrays for type of columns
         *
         * @param col column array from this Table
         * @param other the other Table object
         * @param othercol column array from the other Table
         */
        public void checkColumnsMatch(int[] col, Table other, int[] othercol)
        {

            for (int i = 0; i < col.Length; i++)
            {
                SqlType type = colTypes[col[i]];
                SqlType otherType = other.colTypes[othercol[i]];

                if (type.typeComparisonGroup != otherType.typeComparisonGroup)
                {
                    throw Error.error(ErrorCode.X_42562);
                }
            }
        }

        public void checkColumnsMatch(ColumnSchema column, int colIndex)
        {

            SqlType type = colTypes[colIndex];
            SqlType otherType = column.getDataType();

            if (type.typeComparisonGroup != otherType.typeComparisonGroup)
            {
                throw Error.error(ErrorCode.X_42562);
            }
        }

        /**
         * For removal or addition of columns, constraints and indexes
         *
         * Does not work in this form for FK's as Constraint.ConstraintCore
         * is not transfered to a referencing or referenced table
         */
        public Table moveDefinition(Session session, int newType, ColumnSchema column,
                             Constraint constraint, Index index, int colIndex,
                             int adjust, OrderedHashSet dropConstraints,
                             OrderedHashSet dropIndexes)
        {

            bool newPK = false;

            if (constraint != null
                    && constraint.constType
                       == SchemaObjectConstraintTypes.PRIMARY_KEY)
            {
                newPK = true;
            }

            Table tn = new Table(database, tableName, newType);

            if (tableType == TEMP_TABLE)
            {
                tn.persistenceScope = persistenceScope;
            }

            for (int i = 0; i < getColumnCount(); i++)
            {
                ColumnSchema col = (ColumnSchema)columnList.get(i);

                if (i == colIndex)
                {
                    if (column != null)
                    {
                        tn.addColumn(column);
                    }

                    if (adjust <= 0)
                    {
                        continue;
                    }
                }

                tn.addColumn(col);
            }

            if (getColumnCount() == colIndex)
            {
                tn.addColumn(column);
            }

            int[] pkCols = null;

            if (hasPrimaryKey()
                    && !dropConstraints.contains(
                        getPrimaryConstraint().getName()))
            {
                pkCols = primaryKeyCols;
                pkCols = ArrayUtil.toAdjustedColumnArray(pkCols, colIndex, adjust);
            }
            else if (newPK)
            {
                pkCols = constraint.getMainColumns();
            }

            tn.createPrimaryKey(getIndex(0).getName(), pkCols, false);

            for (int i = 1; i < indexList.Length; i++)
            {
                Index idx = indexList[i];

                if (dropIndexes.contains(idx.getName()))
                {
                    continue;
                }

                int[] colarr = ArrayUtil.toAdjustedColumnArray(idx.getColumns(),
                    colIndex, adjust);

                idx = tn.createIndexStructure(idx.getName(), colarr,
                                              idx.getColumnDesc(), null,
                                              idx.isUnique(), idx.isConstraint(),
                                              idx.isForward());

                tn.addIndex(idx);
            }

            if (index != null)
            {
                tn.addIndex(index);
            }

            EfzArrayList newList = new EfzArrayList();

            if (newPK)
            {
                constraint.core.mainIndex = tn.indexList[0];
                constraint.core.mainTable = tn;
                constraint.core.mainTableName = tn.tableName;

                newList.add(constraint);
            }

            for (int i = 0; i < constraintList.Length; i++)
            {
                Constraint c = constraintList[i];

                if (dropConstraints.contains(c.getName()))
                {
                    continue;
                }

                c = c.duplicate();

                c.updateTable(session, this, tn, colIndex, adjust);
                newList.add(c);
            }

            if (!newPK && constraint != null)
            {
                constraint.updateTable(session, this, tn, -1, 0);
                newList.add(constraint);
            }

            tn.constraintList = new Constraint[newList.size()];

            newList.toArray(tn.constraintList);
            tn.updateConstraintLists();
            tn.setBestRowIdentifiers();

            tn.triggerList = triggerList;
            tn.triggerLists = triggerLists;

            return tn;
        }

        /**
         * Used for drop / retype column.
         */
        public void checkColumnInCheckConstraint(int colIndex)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.constType == SchemaObjectConstraintTypes.CHECK
                        && !c.isNotNull() && c.hasColumn(colIndex))
                {
                    QNameManager.QName name = c.getName();

                    throw Error.error(ErrorCode.X_42502,
                                      name.getSchemaQualifiedStatementName());
                }
            }
        }

        /**
         * Used for retype column. Checks whether column is in an FK or is
         * referenced by a FK
         * @param colIndex index
         */
        public void checkColumnInFKConstraint(int colIndex)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.hasColumn(colIndex) && (c.getConstraintType() == SchemaObjectConstraintTypes.MAIN || c
                        .getConstraintType() == SchemaObjectConstraintTypes
                        .FOREIGN_KEY))
                {
                    QNameManager.QName name = c.getName();

                    throw Error.error(ErrorCode.X_42533,
                                      name.getSchemaQualifiedStatementName());
                }
            }
        }

        /**
         * Returns list of constraints dependent only on one column
         */
        public OrderedHashSet getDependentConstraints(int colIndex)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.hasColumnOnly(colIndex))
                {
                    set.add(c);
                }
            }

            return set;
        }

        /**
         * Returns list of constraints dependent on more than one column
         */
        public OrderedHashSet getContainingConstraints(int colIndex)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.hasColumnPlus(colIndex))
                {
                    set.add(c);
                }
            }

            return set;
        }

        public OrderedHashSet getContainingIndexNames(int colIndex)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0, size = indexList.Length; i < size; i++)
            {
                Index index = indexList[i];

                if (ArrayUtil.find(index.getColumns(), colIndex) != -1)
                {
                    set.add(index.getName());
                }
            }

            return set;
        }

        /**
         * Returns list of MAIN constraints dependent on this PK or UNIQUE constraint
         */
        public OrderedHashSet getDependentConstraints(Constraint constraint)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.getConstraintType() == SchemaObjectConstraintTypes.MAIN)
                {
                    if (c.core.uniqueName == constraint.getName())
                    {
                        set.add(c);
                    }
                }
            }

            return set;
        }

        public OrderedHashSet getDependentExternalConstraints()
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.getConstraintType() == SchemaObjectConstraintTypes.MAIN
                        || c.getConstraintType()
                           == SchemaObjectConstraintTypes.FOREIGN_KEY)
                {
                    if (c.core.mainTable != c.core.refTable)
                    {
                        set.add(c);
                    }
                }
            }

            return set;
        }

        /**
         * Used for column defaults and nullability. Checks whether column is in an
         * FK with a given referential action type.
         *
         * @param colIndex index of column
         * @param actionType referential action of the FK
         */
        public void checkColumnInFKConstraint(int colIndex, int actionType)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                Constraint c = constraintList[i];

                if (c.getConstraintType() == SchemaObjectConstraintTypes
                        .FOREIGN_KEY && c
                        .hasColumn(colIndex) && (actionType == c
                            .getUpdateAction() || actionType == c
                            .getDeleteAction()))
                {
                    QNameManager.QName name = c.getName();

                    throw Error.error(ErrorCode.X_42533,
                                      name.getSchemaQualifiedStatementName());
                }
            }
        }

        /**
         *  Returns the identity column index.
         */
        public int getIdentityColumnIndex()
        {
            return identityColumn;
        }

        /**
         *  Returns the index of given column name or throws if not found
         */
        public int getColumnIndex(String name)
        {

            int i = findColumn(name);

            if (i == -1)
            {
                throw Error.error(ErrorCode.X_42501, name);
            }

            return i;
        }

        /**
         *  Returns the index of given column name or -1 if not found.
         */
        public int findColumn(String name)
        {

            int index = columnList.getIndex(name);

            return index;
        }

        /**
         * Sets the SQL default value for a columm.
         */
        public void setDefaultExpression(int columnIndex, Expression def)
        {

            ColumnSchema column = getColumn(columnIndex);

            column.setDefaultExpression(def);
            setColumnTypeVars(columnIndex);
        }

        /**
         * sets the flag for the presence of any default expression
         */
        public void resetDefaultsFlag()
        {

            hasDefaultValues = false;

            for (int i = 0; i < colDefaults.Length; i++)
            {
                hasDefaultValues = hasDefaultValues || colDefaults[i] != null;
            }
        }

        public int[] getBestRowIdentifiers()
        {
            return bestRowIdentifierCols;
        }

        public bool isBestRowIdentifiersStrict()
        {
            return bestRowIdentifierStrict;
        }

        /**
         *  Finds an existing index for a column
         */
        public Index getIndexForColumn(int col)
        {

            int i = bestIndexForColumn[col];

            return i == -1 ? null
                           : this.indexList[i];
        }

        public bool isIndexed(int colIndex)
        {
            return bestIndexForColumn[colIndex] != -1;
        }

        public int[] getUniqueNotNullColumnGroup(bool[] usedColumns)
        {

            for (int i = 0, count = constraintList.Length; i < count; i++)
            {
                Constraint constraint = constraintList[i];

                if (constraint.constType == SchemaObjectConstraintTypes.UNIQUE)
                {
                    int[] indexCols = constraint.getMainColumns();

                    if (ArrayUtil.areIntIndexesInBooleanArray(
                            indexCols, colNotNull) && ArrayUtil
                                .areIntIndexesInBooleanArray(
                                    indexCols, usedColumns))
                    {
                        return indexCols;
                    }
                }
                else if (constraint.constType
                         == SchemaObjectConstraintTypes.PRIMARY_KEY)
                {
                    int[] indexCols = constraint.getMainColumns();

                    if (ArrayUtil.areIntIndexesInBooleanArray(indexCols,
                            usedColumns))
                    {
                        return indexCols;
                    }
                }
            }

            return null;
        }

        public bool areColumnsNotNull(int[] indexes)
        {
            return ArrayUtil.areIntIndexesInBooleanArray(indexes, colNotNull);
        }

        /**
         *  Shortcut for creating system table PK's.
         */
        public void createPrimaryKey(int[] cols)
        {
            createPrimaryKey(null, cols, false);
        }

        /**
         *  Shortcut for creating default PK's.
         */
        public void createPrimaryKey()
        {
            createPrimaryKey(null, null, false);
        }

        /**
         *  Creates a single or multi-column primary key and index. sets the
         *  colTypes array. Finalises the creation of the table. (fredt@users)
         */
        public void createPrimaryKey(QNameManager.QName indexName, int[] columns,
                                     bool columnsNotNull)
        {

            if (primaryKeyCols != null)
            {
                throw Error.runtimeError(ErrorCode.U_S0500, "Table");
            }

            if (columns == null)
            {
                columns = new int[0];
            }
            else
            {
                for (int i = 0; i < columns.Length; i++)
                {
                    getColumn(columns[i]).setPrimaryKey(true);
                }
            }

            primaryKeyCols = columns;

            setColumnStructures();

            primaryKeyTypes = new SqlType[primaryKeyCols.Length];

            ArrayUtil.projectRow(colTypes, primaryKeyCols, primaryKeyTypes);

            primaryKeyColsSequence = new int[primaryKeyCols.Length];

            ArrayUtil.fillSequence(primaryKeyColsSequence);

            QNameManager.QName name = indexName;

            if (name == null)
            {
                name = database.nameManager.newAutoName("IDX", getSchemaName(),
                        getName(), SchemaObjectTypes.INDEX);
            }

            createPrimaryIndex(primaryKeyCols, primaryKeyTypes, name);
            setBestRowIdentifiers();
        }

        public void setColumnStructures()
        {

            colTypes = new SqlType[columnCount];
            colDefaults = new Expression[columnCount];
            colNotNull = new bool[columnCount];
            colGenerated = new bool[columnCount];
            defaultColumnMap = new int[columnCount];

            for (int i = 0; i < columnCount; i++)
            {
                setColumnTypeVars(i);
            }

            resetDefaultsFlag();

            defaultRanges = new RangeVariable[] { new RangeVariable(this, 1) };
        }

        public void setColumnTypeVars(int i)
        {

            ColumnSchema column = getColumn(i);

            colTypes[i] = column.getDataType();
            colNotNull[i] = column.isPrimaryKey() || !column.isNullable();
            defaultColumnMap[i] = i;

            if (column.isIdentity())
            {
                identitySequence = column.getIdentitySequence();
                identityColumn = i;
            }
            else if (identityColumn == i)
            {
                identityColumn = -1;
            }

            colDefaults[i] = column.getDefaultExpression();
            colGenerated[i] = column.isGenerated();

            resetDefaultsFlag();
        }

        /**
         * Returns direct mapping array.
         */
        public int[] getColumnMap()
        {
            return defaultColumnMap;
        }

        /**
         * Returns empty mapping array.
         */
        public int[] getNewColumnMap()
        {
            return new int[getColumnCount()];
        }

        public bool[] getColumnCheckList(int[] columnIndexes)
        {

            bool[] columnCheckList = new bool[getColumnCount()];

            for (int i = 0; i < columnIndexes.Length; i++)
            {
                int index = columnIndexes[i];

                if (index > -1)
                {
                    columnCheckList[index] = true;
                }
            }

            return columnCheckList;
        }

        public int[] getColumnIndexes(OrderedHashSet set)
        {

            int[] cols = new int[set.size()];

            for (int i = 0; i < cols.Length; i++)
            {
                cols[i] = getColumnIndex((String)set.get(i));
            }

            return cols;
        }

        public int[] getColumnIndexes(HashMappedList list)
        {

            int[] cols = new int[list.size()];

            for (int i = 0; i < cols.Length; i++)
            {
                cols[i] = Convert.ToInt32(list.get(i));
            }

            return cols;
        }

        /**
         *  Returns the Column object at the given index
         */
        public ColumnSchema getColumn(int i)
        {
            return (ColumnSchema)columnList.get(i);
        }

        public OrderedHashSet getColumnNameSet(int[] columnIndexes)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0; i < columnIndexes.Length; i++)
            {
                set.add(((ColumnSchema)columnList.get(i)).getName());
            }

            return set;
        }

        public OrderedHashSet getColumnNameSet(bool[] columnCheckList)
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0; i < columnCheckList.Length; i++)
            {
                if (columnCheckList[i])
                {
                    set.add(columnList.get(i));
                }
            }

            return set;
        }

        public void getColumnNames(bool[] columnCheckList, Set set)
        {

            for (int i = 0; i < columnCheckList.Length; i++)
            {
                if (columnCheckList[i])
                {
                    set.add(((ColumnSchema)columnList.get(i)).getName());
                }
            }
        }

        public OrderedHashSet getColumnNameSet()
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0; i < getColumnCount(); i++)
            {
                set.add(((ColumnSchema)columnList.get(i)).getName());
            }

            return set;
        }

        /**
         * Returns array for a new row with SQL DEFAULT value for each column n
         * where exists[n] is false. This provides default values only where
         * required and avoids evaluating these values where they will be
         * overwritten.
         */
        public Object[] getNewRowData(Session session)
        {

            Object[] data = new Object[getColumnCount()];
            int i;

            if (hasDefaultValues)
            {
                for (i = 0; i < getColumnCount(); i++)
                {
                    Expression def = colDefaults[i];

                    if (def != null)
                    {
                        data[i] = def.getValue(session, colTypes[i]);
                    }
                }
            }

            return data;
        }

        public bool hasTrigger(int trigVecIndex)
        {
            return triggerLists[trigVecIndex].Length != 0;
        }

        /**
         * Adds a trigger.
         */
        public void addTrigger(TriggerDef td, QNameManager.QName otherName)
        {

            int index = triggerList.Length;

            if (otherName != null)
            {
                int pos = getTriggerIndex(otherName.name);

                if (pos != -1)
                {
                    index = pos + 1;
                }
            }

            triggerList = (TriggerDef[])ArrayUtil.toAdjustedArray(triggerList,
                    td, index, 1);

            TriggerDef[] list = triggerLists[td.triggerType];

            index = list.Length;

            if (otherName != null)
            {
                for (int i = 0; i < list.Length; i++)
                {
                    TriggerDef trigger = list[i];

                    if (trigger.name.name.Equals(otherName.name))
                    {
                        index = i + 1;

                        break;
                    }
                }
            }

            list = (TriggerDef[])ArrayUtil.toAdjustedArray(list, td, index, 1);
            triggerLists[td.triggerType] = list;
        }

        /**
         * Returns a trigger.
         */
        public TriggerDef getTrigger(String name)
        {

            for (int i = triggerList.Length - 1; i >= 0; i--)
            {
                if (triggerList[i].name.name.Equals(name))
                {
                    return triggerList[i];
                }
            }

            return null;
        }

        public int getTriggerIndex(String name)
        {

            for (int i = 0; i < triggerList.Length; i++)
            {
                if (triggerList[i].name.name.Equals(name))
                {
                    return i;
                }
            }

            return -1;
        }

        /**
         * Drops a trigger.
         */
        public void removeTrigger(String name)
        {

            TriggerDef td = null;

            for (int i = 0; i < triggerList.Length; i++)
            {
                td = triggerList[i];

                if (td.name.name.Equals(name))
                {
                    td.terminate();

                    triggerList =
                        (TriggerDef[])ArrayUtil.toAdjustedArray(triggerList,
                            null, i, -1);

                    break;
                }
            }

            if (td == null)
            {
                return;
            }

            int index = td.triggerType;

            // look in each trigger in list
            for (int j = 0; j < triggerLists[index].Length; j++)
            {
                td = triggerLists[index][j];

                if (td.name.name.Equals(name))
                {
                    td.terminate();

                    triggerLists[index] = (TriggerDef[])ArrayUtil.toAdjustedArray(
                        triggerLists[index], null, j, -1);

                    break;
                }
            }
        }

        /**
         * Drops all triggers.
         */
        public void releaseTriggers()
        {

            // look in each trigger list of each type of trigger
            for (int i = 0; i < TriggerDef.NUM_TRIGS; i++)
            {
                for (int j = 0; j < triggerLists[i].Length; j++)
                {
                    triggerLists[i][j].terminate();
                }

                triggerLists[i] = TriggerDef.emptyArray;
            }
        }

        /**
         * Returns the index of the Index object of the given name or -1 if not found.
         */
        public int getIndexIndex(String indexName)
        {

            Index[] indexes = indexList;

            for (int i = 0; i < indexes.Length; i++)
            {
                if (indexName.Equals(indexes[i].getName().name))
                {
                    return i;
                }
            }

            // no such index
            return -1;
        }

        /**
         * Returns the Index object of the given name or null if not found.
         */
        public Index getIndex(String indexName)
        {

            Index[] indexes = indexList;
            int i = getIndexIndex(indexName);

            return i == -1 ? null
                           : indexes[i];
        }

        /**
         *  Return the position of the constraint within the list
         */
        public int getConstraintIndex(String constraintName)
        {

            for (int i = 0, size = constraintList.Length; i < size; i++)
            {
                if (constraintList[i].getName().name.Equals(constraintName))
                {
                    return i;
                }
            }

            return -1;
        }

        /**
         *  return the named constriant
         */
        public Constraint getConstraint(String constraintName)
        {

            int i = getConstraintIndex(constraintName);

            return (i < 0) ? null
                           : constraintList[i];
        }

        /**
         * remove a named constraint
         */
        public void removeConstraint(String name)
        {

            int index = getConstraintIndex(name);

            if (index != -1)
            {
                removeConstraint(index);
            }
        }

        public void removeConstraint(int index)
        {

            constraintList =
                (Constraint[])ArrayUtil.toAdjustedArray(constraintList, null,
                    index, -1);

            updateConstraintLists();
        }

        public void renameColumn(ColumnSchema column, String newName, bool isquoted)
        {

            String oldname = column.getName().name;
            int i = getColumnIndex(oldname);

            columnList.setKey(i, newName);
            column.getName().rename(newName, isquoted);
        }

        public void renameColumn(ColumnSchema column, QNameManager.QName newName)
        {

            String oldname = column.getName().name;
            int i = getColumnIndex(oldname);

            if (findColumn(newName.name) != -1)
            {
                throw Error.error(ErrorCode.X_42504);
            }

            columnList.setKey(i, newName.name);
            column.getName().rename(newName);
        }

        public TriggerDef[] getTriggers()
        {
            return triggerList;
        }

        public virtual bool isWritable()
        {
            return !isReadOnly() && !database.databaseReadOnly
                   && !(database.isFilesReadOnly() && (_isCached || _isText));
        }

        public virtual bool isInsertable()
        {
            return isWritable();
        }

        public virtual bool isUpdatable()
        {
            return isWritable();
        }

        public virtual int[] getUpdatableColumns()
        {
            return defaultColumnMap;
        }

        public virtual Table getBaseTable()
        {
            return this;
        }

        public virtual int[] getBaseTableColumnMap()
        {
            return defaultColumnMap;
        }

        //

        /**
         *  Used to create an index automatically for system tables.
         */
        public Index createIndexForColumns(int[] columns)
        {

            QNameManager.QName indexName = database.nameManager.newAutoName("IDX_T",
                getSchemaName(), getName(), SchemaObjectTypes.INDEX);

            try
            {
                Index index = createAndAddIndexStructure(indexName, columns, null,
                    null, false, false, false);

                return index;
            }
            catch (Exception )
            {
                return null;
            }
        }

        public void fireTriggers(Session session, int trigVecIndex,
                          HashMappedList rowSet)
        {

            TriggerDef[] trigVec = triggerLists[trigVecIndex];

            for (int i = 0, size = trigVec.Length; i < size; i++)
            {
                TriggerDef td = trigVec[i];
                bool sqlTrigger = td is TriggerDefSQL;

                if (td.isForEachRow())
                {
                    for (int j = 0; j < rowSet.size(); j++)
                    {
                        Object[] oldData = ((Row)rowSet.getKey(j)).getData();
                        Object[] newData = (Object[])rowSet.get(j);

                        switch (td.triggerType)
                        {

                            case TriggerConstants.UPDATE_AFTER_ROW:
                                if (!sqlTrigger)
                                {
                                    oldData = (Object[])ArrayUtil.duplicateArray(
                                        oldData);
                                    newData = (Object[])ArrayUtil.duplicateArray(
                                        newData);
                                }
                                break;
                        }

                        td.pushPair(session, oldData, newData);
                    }
                }
                else
                {
                    td.pushPair(session, null, null);
                }
            }
        }

        public void fireTriggers(Session session, int trigVecIndex,
                           RowSetNavigator rowSet)
        {

            if (!database.isReferentialIntegrity())
            {
                return;
            }

            TriggerDef[] trigVec = triggerLists[trigVecIndex];

            for (int i = 0, size = trigVec.Length; i < size; i++)
            {
                TriggerDef td = trigVec[i];
                bool sqlTrigger = td is TriggerDefSQL;

                if (td.hasOldTable())
                {

                    //
                }

                if (td.isForEachRow())
                {
                    while (rowSet.hasNext())
                    {
                        Object[] oldData = null;
                        Object[] newData = null;

                        switch (td.triggerType)
                        {

                            case TriggerConstants.DELETE_BEFORE_ROW:
                            case TriggerConstants.DELETE_AFTER_ROW:
                                oldData = rowSet.getNext();

                                if (!sqlTrigger)
                                {
                                    oldData = (Object[])ArrayUtil.duplicateArray(
                                        oldData);
                                }
                                break;

                            case TriggerConstants.INSERT_BEFORE_ROW:
                            case TriggerConstants.INSERT_AFTER_ROW:
                                newData = rowSet.getNext();

                                if (!sqlTrigger)
                                {
                                    newData = (Object[])ArrayUtil.duplicateArray(
                                        newData);
                                }
                                break;
                        }

                        td.pushPair(session, oldData, newData);
                    }

                    rowSet.beforeFirst();
                }
                else
                {
                    td.pushPair(session, null, null);
                }
            }
        }

        /**
         *  Fires all row-level triggers of the given set (trigger type)
         *
         */
        public void fireTriggers(Session session, int trigVecIndex, Object[] oldData,
                          Object[] newData, int[] cols)
        {

            if (!database.isReferentialIntegrity())
            {
                return;
            }

            TriggerDef[] trigVec = triggerLists[trigVecIndex];

            for (int i = 0, size = trigVec.Length; i < size; i++)
            {
                TriggerDef td = trigVec[i];
                bool sqlTrigger = td is TriggerDefSQL;

                if (cols != null && td.getUpdateColumnIndexes() != null
                        && !ArrayUtil.haveCommonElement(
                            td.getUpdateColumnIndexes(), cols, cols.Length))
                {
                    continue;
                }

                if (td.isForEachRow())
                {
                    switch (td.triggerType)
                    {

                        case TriggerConstants.INSERT_BEFORE_ROW:
                            break;

                        case TriggerConstants.INSERT_AFTER_ROW:
                            if (!sqlTrigger)
                            {
                                newData =
                                    (Object[])ArrayUtil.duplicateArray(newData);
                            }
                            break;

                        case TriggerConstants.UPDATE_AFTER_ROW:
                            if (!sqlTrigger)
                            {
                                oldData =
                                    (Object[])ArrayUtil.duplicateArray(oldData);
                                newData =
                                    (Object[])ArrayUtil.duplicateArray(newData);
                            }
                            break;

                        case TriggerConstants.UPDATE_BEFORE_ROW:
                        case TriggerConstants.DELETE_BEFORE_ROW:
                        case TriggerConstants.DELETE_AFTER_ROW:
                            if (!sqlTrigger)
                            {
                                oldData =
                                    (Object[])ArrayUtil.duplicateArray(oldData);
                            }
                            break;
                    }

                    td.pushPair(session, oldData, newData);
                }
                else
                {
                    td.pushPair(session, null, null);
                }
            }
        }

        /**
         *  Enforce max field sizes according to SQL column definition.
         *  SQL92 13.8
         */
        public void enforceRowConstraints(Session session, Object[] data)
        {

            for (int i = 0; i < defaultColumnMap.Length; i++)
            {
                SqlType type = colTypes[i];

                data[i] = type.convertToTypeLimits(session, data[i]);

                if (type.isDomainType())
                {
                    Constraint[] constraints =
                        type.userTypeModifier.getConstraints();

                    for (int j = 0; j < constraints.Length; j++)
                    {
                        constraints[j].checkCheckConstraint(session, this,
                                                            (Object)data[i]);
                    }
                }

                if (data[i] == null)
                {
                    if (colNotNull[i])
                    {
                        Constraint c = getNotNullConstraintForColumn(i);

                        if (c == null)
                        {
                            c = this.getPrimaryConstraint();
                        }

                        String[] info = new String[] {
                        c.getName().name, tableName.name
                    };

                        throw Error.error(null, ErrorCode.X_23502,
                                          ErrorCode.CONSTRAINT, info);
                    }
                }
            }
        }

        /**
         *  Finds an existing index for a column group
         */
        public Index getIndexForColumns(int[] cols)
        {

            int i = bestIndexForColumn[cols[0]];

            if (i > -1)
            {
                return indexList[i];
            }

            switch (tableType)
            {

                case TableBase.SYSTEM_SUBQUERY:
                case TableBase.SYSTEM_TABLE:
                case TableBase.VIEW_TABLE:
                case TableBase.TEMP_TABLE:
                    {
                        Index index = createIndexForColumns(cols);

                        return index;
                    }
            }

            return null;
        }

        /**
         * Finds an existing index for a column set or create one for temporary
         * tables
         */
        public Index getIndexForColumns(OrderedIntHashSet set)
        {

            int maxMatchCount = 0;
            Index selected = null;

            if (set.isEmpty())
            {
                return null;
            }

            for (int i = 0, count = indexList.Length; i < count; i++)
            {
                Index currentindex = getIndex(i);
                int[] indexcols = currentindex.getColumns();
                int matchCount = set.getOrderedMatchCount(indexcols);

                if (matchCount == 0)
                {
                    continue;
                }

                if (matchCount == indexcols.Length)
                {
                    return currentindex;
                }

                if (matchCount > maxMatchCount)
                {
                    maxMatchCount = matchCount;
                    selected = currentindex;
                }
            }

            if (selected != null)
            {
                return selected;
            }

            switch (tableType)
            {

                case TableBase.SYSTEM_SUBQUERY:
                case TableBase.SYSTEM_TABLE:
                case TableBase.VIEW_TABLE:
                case TableBase.TEMP_TABLE:
                    {
                        selected = createIndexForColumns(set.toArray());
                    }
                    break;
            }

            return selected;
        }

        /**
         *  Return the list of file pointers to root nodes for this table's
         *  indexes.
         */
        public int[] getIndexRootsArray()
        {

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);
            int[] roots = new int[getIndexCount()];

            for (int i = 0; i < getIndexCount(); i++)
            {
                CachedObject accessor = store.getAccessor(indexList[i]);

                roots[i] = accessor == null ? -1
                                            : accessor.getPos();
            }

            return roots;
        }

        /**
         * Returns the string consisting of file pointers to roots of indexes
         * plus the next identity value (hidden or user defined). This is used
         * with CACHED tables.
         */
        static String getIndexRoots(int[] rootsArray)
        {

            String roots = StringUtil.getList(rootsArray, " ", "");
            StringBuilder s = new StringBuilder(roots);

            /*
                    s.Append(' ');
                    s.Append(identitySequence.peek());
            */
            return s.ToString();
        }

        /**
         *  Sets the index roots of a cached/text table to specified file
         *  pointers. If a
         *  file pointer is -1 then the particular index root is null. A null index
         *  root signifies an empty table. Accordingly, all index roots should be
         *  null or all should be a valid file pointer/reference.
         */
        public void setIndexRoots(int[] roots)
        {

            if (!_isCached)
            {
                throw Error.error(ErrorCode.X_42501, tableName.name);
            }

            PersistentStore store =
                database.persistentStoreCollection.getStore(this);

            for (int i = 0; i < getIndexCount(); i++)
            {
                store.setAccessor(indexList[i], roots[i]);
            }
        }

        /**
         *  Sets the index roots and next identity.
         */
       public void setIndexRoots(Session session, String s)
        {

            if (!_isCached)
            {
                throw Error.error(ErrorCode.X_42501, tableName.name);
            }

            ParserDQL p = new ParserDQL(session, new Scanner(s));
            int[] roots = new int[getIndexCount()];

            p.read();

            for (int i = 0; i < getIndexCount(); i++)
            {
                int v = p.readInteger();

                roots[i] = v;
            }

            setIndexRoots(roots);
        }

        /**
         *  Performs Table structure modification and changes to the index nodes
         *  to remove a given index from a MEMORY or TEXT table. Not for PK index.
         *
         */
        public void dropIndex(Session session, String indexname)
        {

            // find the array index for indexname and remove
            int todrop = getIndexIndex(indexname);

            indexList = (Index[])ArrayUtil.toAdjustedArray(indexList, null,
                    todrop, -1);

            for (int i = 0; i < indexList.Length; i++)
            {
                indexList[i].setPosition(i);
            }

            setBestRowIdentifiers();

            if (store != null)
            {
                store.resetAccessorKeys(indexList);
            }
        }

        /**
         * Moves the data from table to table.
         * The colindex argument is the index of the column that was
         * added or removed. The adjust argument is {-1 | 0 | +1}
         */
        public void moveData(Session session, Table from, int colindex, int adjust)
        {

            Object colvalue = null;
            ColumnSchema column = null;

            if (adjust >= 0 && colindex != -1)
            {
                column = getColumn(colindex);
                colvalue = column.getDefaultValue(session);
            }

            PersistentStore store = session.sessionData.getRowStore(this);

            try
            {
                RowIterator it = from.rowIterator(session);

                while (it.hasNext())
                {
                    Row row = it.getNextRow();
                    Object[] o = row.getData();
                    Object[] data = getEmptyRowData();

                    if (adjust == 0 && colindex != -1)
                    {
                        colvalue = column.getDataType().convertToType(session,
                                o[colindex],
                                from.getColumn(colindex).getDataType());
                    }

                    ArrayUtil.copyAdjustArray(o, data, colvalue, colindex, adjust);
                    systemSetIdentityColumn(session, data);
                    enforceRowConstraints(session, data);

                    // get object without RowAction
                    Row newrow = (Row)store.getNewCachedObject(null, data);

                    if (row.rowAction != null)
                    {
                        newrow.rowAction =
                            row.rowAction.duplicate(newrow.getPos());
                    }

                    store.indexRow(null, newrow);
                }
            }
            catch (Exception t)
            {
                store.release();

                if (t is CoreException)
                {
                    throw (CoreException)t;
                }

                throw new CoreException(t, "", 0);
            }
        }

        //

        /**
         *  Mid level method for inserting rows. Performs constraint checks and
         *  fires row level triggers.
         */
        public void insertRow(Session session, PersistentStore store, Object[] data)
        {

            setIdentityColumn(session, data);
            fireTriggers(session, TriggerConstants.INSERT_BEFORE_ROW, null, data, null);
            setGeneratedColumns(session, data);

            if (_isView)
            {
                return;
            }

            enforceRowConstraints(session, data);

            if (database.isReferentialIntegrity())
            {
                for (int i = 0, size = constraintList.Length; i < size; i++)
                {
                    constraintList[i].checkInsert(session, this, data);
                }
            }

            insertNoCheck(session, store, data);
            fireTriggers(session, TriggerConstants.INSERT_AFTER_ROW, null, data, null);
        }

        /**
         * Multi-row insert method. Used for CREATE TABLE AS ... queries.
         */
        public void insertIntoTable(Session session, Result result)
        {

            PersistentStore store = session.sessionData.getRowStore(this);
            RowSetNavigator nav = result.initialiseNavigator();

            while (nav.hasNext())
            {
                Object[] data = (Object[])nav.getNext();
                Object[] newData =
                    (Object[])ArrayUtil.resizeArrayIfDifferent(data,
                        getColumnCount());

                insertData(store, newData);
            }
        }

        /**
         *  Low level method for row insert.
         *  UNIQUE or PRIMARY constraints are enforced by attempting to
         *  add the row to the indexes.
         */
        private Row insertNoCheck(Session session, PersistentStore store,
                                  Object[] data)
        {

            Row row = (Row)store.getNewCachedObject(session, data);

            store.indexRow(session, row);
            session.addInsertAction(this, row);

            return row;
        }

        /**
         *
         */
        public void insertNoCheckFromLog(Session session, Object[] data)
        {

            systemUpdateIdentityValue(data);

            PersistentStore store = session.sessionData.getRowStore(this);
            Row row = (Row)store.getNewCachedObject(session, data);

            store.indexRow(session, row);
            session.addInsertAction(this, row);
        }

        /**
         * Used for system table inserts. No checks. No identity
         * columns.
         */
        public int insertSys(PersistentStore store, Result ins)
        {

            RowSetNavigator nav = ins.getNavigator();
            int count = 0;

            while (nav.hasNext())
            {
                insertSys(store, (Object[])nav.getNext());

                count++;
            }

            return count;
        }

        /**
         * Used for subquery inserts. No checks. No identity
         * columns.
         */
        public void insertResult(PersistentStore store, Result ins)
        {

            RowSetNavigator nav = ins.initialiseNavigator();

            while (nav.hasNext())
            {
                Object[] data = (Object[])nav.getNext();
                Object[] newData =
                    (Object[])ArrayUtil.resizeArrayIfDifferent(data,
                        getColumnCount());

                insertData(store, newData);
            }
        }

        /**
         * Not for general use.
         * Used by ScriptReader to unconditionally insert a row into
         * the table when the .script file is read.
         */
        public void insertFromScript(PersistentStore store, Object[] data)
        {
            systemUpdateIdentityValue(data);
            insertData(store, data);
        }

        /**
         * For system operations outside transaction constrol
         */
        public virtual void insertData(PersistentStore store, Object[] data)
        {

            Row row = (Row)store.getNewCachedObject(null, data);

            store.indexRow(null, row);
        }

        /**
         * Used by the system tables only
         */
        public void insertSys(PersistentStore store, Object[] data)
        {

            Row row = (Row)store.getNewCachedObject(null, data);

            store.indexRow(null, row);
        }

        /**
         * If there is an identity or generated column in the table, sets
         * the value and/or adjusts the identiy value for the table.
         */
        public void setIdentityColumn(Session session, Object[] data)
        {

            if (identityColumn != -1)
            {
                ValueType id = (ValueType)data[identityColumn];

                if (id == null)
                {
                    id = (ValueType)identitySequence.getValueObject();
                    data[identityColumn] = id;
                }
                else
                {
                    identitySequence.userUpdate(Convert.ToInt64(id));
                }

                if (session != null)
                {
                    session.setLastIdentity(id);
                }
            }
        }

        public void setGeneratedColumns(Session session, Object[] data)
        {

            for (int i = 0; i < colGenerated.Length; i++)
            {
                if (colGenerated[i])
                {
                    Expression e = getColumn(i).getGeneratingExpression();
                    RangeVariable.RangeIteratorBase range =
                        session.sessionContext.getCheckIterator(defaultRanges[0]);

                    range.currentData = data;
                    data[i] = e.getValue(session, colTypes[i]);
                }
            }
        }

        protected void systemSetIdentityColumn(Session session, Object[] data)
        {

            if (identityColumn != -1)
            {
                ValueType id = (ValueType)data[identityColumn];

                if (id == null)
                {
                    id = (ValueType)identitySequence.getValueObject();
                    data[identityColumn] = id;
                }
                else
                {
                    identitySequence.userUpdate(Convert.ToInt64(id));
                }
            }
        }

        /**
         * If there is an identity column in the table, sets
         * the max identity value.
         */
        protected void systemUpdateIdentityValue(Object[] data)
        {

            if (identityColumn != -1)
            {
                ValueType id = (ValueType)data[identityColumn];

                if (id != null)
                {
                    identitySequence.systemUpdate(Convert.ToInt64(id));
                }
            }
        }

        /**
         *  Delete method for referential triggered actions.
         */
        public void deleteRowAsTriggeredAction(Session session, Row row)
        {
            deleteNoCheck(session, row);
        }

        /**
         *  Mid level row delete method. Fires triggers but no integrity
         *  constraint checks.
         */
        public void deleteNoRefCheck(Session session, Row row)
        {

            Object[] data = row.getData();

            fireTriggers(session, TriggerConstants.DELETE_BEFORE_ROW, data, null, null);

            if (_isView)
            {
                return;
            }

            deleteNoCheck(session, row);
        }

        /**
         * Low level row delete method. Removes the row from the indexes and
         * from the Cache.
         */
        private void deleteNoCheck(Session session, Row row)
        {

            if (row.isDeleted(session))
            {
                return;
            }

            session.addDeleteAction(this, row);
        }

        /**
         * For log statements. Delete a single row.
         */
        public void deleteNoCheckFromLog(Session session, Object[] data)
        {

            Row row = null;
            PersistentStore store = session.sessionData.getRowStore(this);

            if (hasPrimaryKey())
            {
                RowIterator it = getPrimaryIndex().findFirstRow(session, store,
                    data, primaryKeyColsSequence);

                row = it.getNextRow();
            }
            else if (bestIndex == null)
            {
                RowIterator it = rowIterator(session);

                while (true)
                {
                    row = it.getNextRow();

                    if (row == null)
                    {
                        break;
                    }

                    if (IndexAVL.compareRows(
                            session, row.getData(), data, defaultColumnMap,
                            colTypes) == 0)
                    {
                        break;
                    }
                }
            }
            else
            {
                RowIterator it = bestIndex.findFirstRow(session, store, data);

                while (true)
                {
                    row = it.getNextRow();

                    if (row == null)
                    {
                        break;
                    }

                    Object[] rowdata = row.getData();

                    // reached end of range
                    if (bestIndex.compareRowNonUnique(
                            session, data, bestIndex.getColumns(), rowdata) != 0)
                    {
                        row = null;

                        break;
                    }

                    if (IndexAVL.compareRows(
                            session, rowdata, data, defaultColumnMap,
                            colTypes) == 0)
                    {
                        break;
                    }
                }
            }

            if (row == null)
            {
                return;
            }

            deleteNoCheck(session, row);
        }

        public void updateRowSet(Session session, HashMappedList rowSet, int[] cols,
                          bool isTriggeredSet)
        {

            PersistentStore store = session.sessionData.getRowStore(this);

            for (int i = 0; i < rowSet.size(); i++)
            {
                Row row = (Row)rowSet.getKey(i);

                if (row.isDeleted(session))
                {
                    if (isTriggeredSet)
                    {
                        rowSet.remove(i);

                        i--;

                        continue;
                    }
                    else
                    {
                        throw Error.error(ErrorCode.X_27000);
                    }
                }
            }

            for (int i = 0; i < rowSet.size(); i++)
            {
                Row row = (Row)rowSet.getKey(i);
                Object[] data = (Object[])rowSet.get(i);

                checkRowData(session, data, cols);    // todo - see if check is necessary ??
                deleteNoCheck(session, row);
            }

            for (int i = 0; i < rowSet.size(); i++)
            {
                Object[] data = (Object[])rowSet.get(i);

                insertNoCheck(session, store, data);
            }
        }

        public void addLobUsageCount(Session session, Object[] data)
        {

            if (!hasLobColumn)
            {
                return;
            }

            for (int j = 0; j < columnCount; j++)
            {
                if (colTypes[j].isLobType())
                {
                    Object value = data[j];

                    if (value == null)
                    {
                        continue;
                    }

                    session.sessionData.addLobUsageCount(
                        ((LobData)value).getId());
                }
            }
        }

        public void removeLobUsageCount(Session session, Object[] data)
        {

            if (!hasLobColumn)
            {
                return;
            }

            for (int j = 0; j < columnCount; j++)
            {
                if (colTypes[j].isLobType())
                {
                    Object value = data[j];

                    if (value == null)
                    {
                        continue;
                    }

                    session.sessionData.removeUsageCount(
                        ((LobData)value).getId());
                }
            }
        }

        public void checkRowData(Session session, Object[] data, int[] cols)
        {
            enforceRowConstraints(session, data);

            for (int i = 0; i < checkConstraints.Length; i++)
            {
                checkConstraints[i].checkCheckConstraint(session, this, data);
            }
        }

        public override void clearAllData(Session session)
        {

            base.clearAllData(session);

            if (identitySequence != null)
            {
                identitySequence.reset();
            }
        }

        public override void clearAllData(PersistentStore store)
        {

            base.clearAllData(store);

            if (identitySequence != null)
            {
                identitySequence.reset();
            }
        }
    }
}
